﻿namespace LMT.Communication.Tcp
{
    using System;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    using DotNet = System.Net.Sockets;

    /// <summary>
    /// 
    /// </summary>
    public class TcpClient
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="data">The data.</param>
        public delegate void ReceiveDataHandler(byte[] data);

        /// <summary>
        /// Occurs when [receive data event].
        /// </summary>
        public event ReceiveDataHandler ReceiveDataEvent;

        /// <summary>
        /// 
        /// </summary>
        public delegate void ConnectionHandler();

        /// <summary>
        /// Occurs when [disconnect from server].
        /// </summary>
        public event ConnectionHandler DisconnectFromServer;

        /// <summary>
        /// 
        /// </summary>
        private DotNet.TcpClient tcpClient;

        /// <summary>
        /// 
        /// </summary>
        private IPAddress serverIp;

        /// <summary>
        /// 
        /// </summary>
        private int port;

        /// <summary>
        /// Gets a value indicating whether this instance is connected.
        /// </summary>
        /// <value>
        ///     <c>true</c> if this instance is connected; otherwise, <c>false</c>.
        /// </value>
        public bool IsConnected { get; private set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="TcpClient"/> class.
        /// </summary>
        /// <param name="serverIp">The server ip.</param>
        /// <param name="port">The port.</param>
        /// <param name="bufferSize">Size of the buffer.</param>
        public TcpClient(string serverIp, int port, int? bufferSize)
        {
            this.serverIp = IPAddress.Parse(serverIp);
            this.port = port;

            if (TcpHelper.Instance == null)
            {
                TcpHelper.CreateIntance(bufferSize);
            }
        }

        /// <summary>
        /// Starts this instance.
        /// </summary>
        public void Start()
        {
            this.InternalInitSocket();

            this.tcpClient.Connect(this.serverIp, this.port);

            this.IsConnected = true;

            Thread waitForServer = new Thread(delegate()
            {
                this.WaitForServer();
            });
            waitForServer.IsBackground = true;
            waitForServer.Start();
        }

        /// <summary>
        /// Stops this instance.
        /// </summary>
        public void Stop()
        {
            this.tcpClient.Client.Shutdown(DotNet.SocketShutdown.Both);
            this.tcpClient.Client.Disconnect(false);
            this.tcpClient.Close();
            this.tcpClient = null;
        }

        /// <summary>
        /// Sends the data to server.
        /// </summary>
        /// <param name="data">The data.</param>
        public void Send(byte[] data)
        {
            TcpHelper.Instance.SendData(this.tcpClient.GetStream(), data);
        }

        /// <summary>
        /// Waits for server.
        /// </summary>
        private void WaitForServer()
        {
            try
            {
                while (this.IsConnected)
                {
                    byte[] data = TcpHelper.Instance.ReadData(this.tcpClient.GetStream());
                    if (data != null && this.ReceiveDataEvent != null)
                    {
                        this.ReceiveDataEvent(data);
                    }
                }
            }
            catch (Exception ex)
            {
                this.ProcessConnectionException(ex);
            }
        }

        /// <summary>
        /// Internals the init socket.
        /// </summary>
        private void InternalInitSocket()
        {
            this.tcpClient = new DotNet.TcpClient();
            this.tcpClient.Client.ReceiveBufferSize = TcpHelper.Instance.SizeOfBlockBuffer;
            this.tcpClient.Client.SendBufferSize = TcpHelper.Instance.SizeOfBlockBuffer;
        }

        /// <summary>
        /// Fires the disconnect server.
        /// </summary>
        /// <param name="ex">The ex.</param>
        private void ProcessConnectionException(Exception ex)
        {
            SocketException se = ex as SocketException;
            if (se != null)
            {
                if (se.ErrorCode == 10054)
                {
                    this.IsConnected = false;
                }
            }
            else if ((ex as IOException) != null)
            {
                if (ex.InnerException != null)
                {
                    se = ex.InnerException as SocketException;
                    if (se != null)
                    {
                        if (se.ErrorCode == 10054)
                        {
                            this.IsConnected = false;
                        }
                    }
                }
            }

            if (!this.IsConnected)
            {
                this.Stop();
            }

            if (this.DisconnectFromServer != null)
            {
                this.DisconnectFromServer();
            }
        }
    }
}